package cn.yunbamboos.rest.service.core;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.yunbamboos.rest.service.IBeanInvoke;
import cn.yunbamboos.rest.service.ICreateInDTO;
import cn.yunbamboos.rest.service.ICustomClassLoader;
import cn.yunbamboos.rest.service.IRestServiceList;
import cn.yunbamboos.rest.service.anno.RestServiceAfterFilters;
import cn.yunbamboos.rest.service.anno.RestServiceDTO;
import cn.yunbamboos.rest.service.anno.RestServiceFilters;
import cn.yunbamboos.rest.service.anno.RestServiceMapping;
import cn.yunbamboos.rest.service.anno.RestServiceTransaction;
import cn.yunbamboos.rest.service.dto.in.BaseInDTO;
import cn.yunbamboos.rest.service.dto.in.DefaultInDTO;
import cn.yunbamboos.rest.service.dto.out.BaseOutDTO;
import cn.yunbamboos.rest.service.dto.out.DefaultOutDTO;
import cn.yunbamboos.rest.service.filter.RestServiceFilter;
import cn.yunbamboos.rest.service.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;

@Order(Integer.MIN_VALUE)
public class ReadRestService implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(ReadRestService.class);

    private final String projectPath;
    private final String restServicePath;
    private final ApplicationContext applicationContext;
    private final IRestServiceList restServiceList;
    private final ICustomClassLoader customClassLoader;

    public ReadRestService(String projectPath, String restServicePath, ApplicationContext applicationContext, IRestServiceList restServiceList, ICustomClassLoader customClassLoader) {
        this.projectPath = projectPath;
        this.restServicePath = restServicePath;
        this.applicationContext = applicationContext;
        this.restServiceList = restServiceList;
        this.customClassLoader = customClassLoader;
    }

    @Override
    public void run(String... args) throws Exception {
        String[] beanNames = applicationContext.getBeanNamesForAnnotation(Service.class);
        for (String beanName : beanNames) {
            Object bean = applicationContext.getBean(beanName);
            // 获取bean的原生类
            Class<?> clazz = AopProxyUtils.ultimateTargetClass(bean);
            Class<?> inter = getInterFromBeanClass(clazz);
            // 获取方法注解
            Method[] methods = clazz.getMethods();
            logger.info("read bean {} {}", beanName, inter);
            try {
                for (Method method : methods) {
                    RestServiceMapping mapping = method.getAnnotation(RestServiceMapping.class);
                    if (ObjUtil.isNotNull(mapping)) {
                        String returnType = getReturnType(method);
                        createRestService(method, mapping, bean, inter, method.getName(), returnType);
                    }
                }
            } catch (Throwable e) {
                logger.error(StrUtil.format("create rest service error {} ", inter), e);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void createRestService(Method method, RestServiceMapping mapping, Object bean, Class<?> inter, String methodName, String returnType) {
        String[] urls = mapping.url();
        Class<? extends BaseInDTO> in = DefaultInDTO.class;
        Class<? extends BaseOutDTO> out = DefaultOutDTO.class;
        Transaction transaction = Transaction.READ;
        Class<? extends RestServiceFilter>[] filters = new Class[]{};
        Class<? extends RestServiceFilter>[] afterFilters = new Class[]{};
        RestServiceDTO dto = method.getAnnotation(RestServiceDTO.class);
        if (ObjUtil.isNotNull(dto)) {
            in = dto.in();
            out = dto.out();
        }
        RestServiceTransaction restServiceTransaction = method.getAnnotation(RestServiceTransaction.class);
        if (ObjUtil.isNotNull(restServiceTransaction)) {
            transaction = restServiceTransaction.value();
        }
        // 过滤器
        RestServiceFilters restServiceFilters = method.getAnnotation(RestServiceFilters.class);
        if (ObjUtil.isNotNull(restServiceFilters)) {
            filters = restServiceFilters.value();
        }
        // 后置过滤器
        RestServiceAfterFilters restServiceAfterFilters = method.getAnnotation(RestServiceAfterFilters.class);
        if (ObjUtil.isNotNull(restServiceAfterFilters)) {
            afterFilters = restServiceAfterFilters.value();
        }
        for (String url : urls) {
            RestService.RestServiceBuilder builder = new RestService.RestServiceBuilder();
            builder.url(projectPath + restServicePath + url)
                    .name(mapping.name()) // 服务名称
                    .authentication(mapping.authentication())// 登陆验证
                    .log(mapping.log()) // 记录日志
                    .transaction(transaction)
                    .in(in)
                    .out(out)
                    .createInDTO(createDTO(in))
                    .filters(filters)
                    .afterFilters(afterFilters);
            builder.invoke(createBeanInvoke(bean, inter, methodName, in, out, returnType));
            restServiceList.add(builder.build());
        }
    }

    // 创建入参DTO ICreateDTO
    private ICreateInDTO createDTO(Class<? extends BaseInDTO> clazz) {
        return customClassLoader.getCreateDTOInstance(clazz);
    }

    // 创建调用服务Invoke  IBeanInvoke
    private IBeanInvoke createBeanInvoke(Object bean, Class<?> inter, String methodName, Class<? extends BaseInDTO> in, Class<? extends BaseOutDTO> out, String returnType) {
        return customClassLoader.getBeanInvokeInstance(bean, inter, methodName, in, out, returnType);
    }

    private Class<?> getInterFromBeanClass(Class<?> clazz) {
        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class<?> inter : interfaces) {
            String name = inter.getSimpleName();
            if (name.startsWith("I") && name.endsWith("Service")) {
                return inter;
            }
        }
        return clazz;
    }

    private String getReturnType(Method method) {
        Class<?> returnType = method.getReturnType();
        return org.objectweb.asm.Type.getInternalName(returnType);
    }

}
